#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Version        : 1.2
# @Update Time    : 2025/8/26 10:30
# @File           : local_file.py
# @IDE            : PyCharm
# @Desc           : 本地文件上传策略，支持普通文件、图片、音频和视频的上传，增强版本
import os
import shutil
from typing import Optional, List
from pathlib import Path
from fastapi import UploadFile, BackgroundTasks

from applications.core.exceptions import BaseAppException
from applications.utils.upload_file.base import AbstractUpload, UploadResult
from applications.utils.upload_file.file_utils import FileUtils


class LocalFileStrategy(AbstractUpload):
    """本地文件上传策略

    实现本地文件系统的文件上传、删除和查询功能，支持分片上传、缩略图生成等功能。
    """

    # 分片大小 (5MB)
    CHUNK_SIZE = 5 * 1024 * 1024

    @classmethod
    def get_storage_strategy(cls) -> str:
        """获取存储策略类型"""
        return "local"

    async def upload_with_result(
        self,
        path: str,
        file: UploadFile,
        accept: Optional[List[str]] = None,
        max_size: int = 5,
        create_thumbnail: bool = False,
    ) -> UploadResult:
        """上传文件并返回详细结果

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)
            create_thumbnail: 是否创建缩略图

        Returns:
            UploadResult: 上传结果对象
        """
        if not accept:
            accept = self.ALL_ACCEPT

        return await self.save_with_advanced_options(
            path=path, file=file, accept=accept, max_size=max_size, create_thumbnail=create_thumbnail
        )

    async def batch_upload(
        self, path: str, files: List[UploadFile], accept: Optional[List[str]] = None, max_size: int = 5
    ) -> List[UploadResult]:
        """批量上传文件

        Args:
            path: 保存路径
            files: 上传的文件列表
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            List[UploadResult]: 上传结果列表
        """
        results = []
        for file in files:
            try:
                result = await self.upload_with_result(path, file, accept, max_size)
                results.append(result)
            except Exception as e:
                error_result = UploadResult(success=False, error=str(e), message=f"文件 {file.filename} 上传失败")
                results.append(error_result)
        return results

    async def get_file_url(self, file_path: str) -> str:
        """获取文件访问URL

        Args:
            file_path: 文件路径（相对于静态文件目录的相对路径）

        Returns:
            str: 文件访问URL
        """
        # 从配置文件获取静态文件URL路径前缀
        from applications.config import settings

        static_url_path = settings.STATIC_URL_PATH

        # 清理路径，确保没有重复的斜杠
        clean_path = file_path.lstrip("/")

        # 组合URL路径
        return f"{static_url_path.rstrip('/')}/{clean_path}"

    async def file_exists(self, file_path: str) -> bool:
        """检查文件是否存在

        Args:
            file_path: 文件路径（相对路径或绝对路径）

        Returns:
            bool: 文件是否存在
        """
        # 如果是相对路径，转换为物理路径
        if not os.path.isabs(file_path):
            file_path = self.get_physical_path(file_path)
        return os.path.exists(file_path)

    async def upload_file(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传普通文件

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问路径

        Raises:
            BaseAppException: 文件验证失败时抛出
        """
        if not accept:
            accept = self.FILE_ACCEPT
        result = await self._save_file_enhanced(path, file, accept, max_size)
        if not result.file_path:
            raise BaseAppException(msg="文件上传失败，未获取到文件路径")
        return result.file_path

    async def upload_image(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传图片文件

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问路径

        Raises:
            BaseAppException: 文件验证失败时抛出
        """
        if not accept:
            accept = self.IMAGE_ACCEPT
        result = await self._save_file_enhanced(path, file, accept, max_size, create_thumbnail=True)
        if not result.file_path:
            raise BaseAppException(msg="图片上传失败，未获取到文件路径")
        return result.file_path

    async def upload_audio(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传音频文件

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问路径

        Raises:
            BaseAppException: 文件验证失败时抛出
        """
        if not accept:
            accept = self.AUDIO_ACCEPT
        result = await self._save_file_enhanced(path, file, accept, max_size)
        if not result.file_path:
            raise BaseAppException(msg="音频上传失败，未获取到文件路径")
        return result.file_path

    async def upload_video(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 5
    ) -> str:
        """上传视频文件

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问路径

        Raises:
            BaseAppException: 文件验证失败时抛出
        """
        if not accept:
            accept = self.VIDEO_ACCEPT
        result = await self._save_file_enhanced(path, file, accept, max_size)
        if not result.file_path:
            raise BaseAppException(msg="视频上传失败，未获取到文件路径")
        return result.file_path

    async def upload_large_file(
        self, path: str, file: UploadFile, accept: Optional[List[str]] = None, max_size: int = 50
    ) -> str:
        """上传大文件（单文件上传，非分片）

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问路径

        Raises:
            BaseAppException: 文件验证失败时抛出
        """
        if not accept:
            accept = self.ALL_ACCEPT
        result = await self._save_file_enhanced(path, file, accept, max_size)
        if not result.file_path:
            raise BaseAppException(msg="大文件上传失败，未获取到文件路径")
        return result.file_path

    async def delete_file(self, file_path: str) -> bool:
        """删除文件

        Args:
            file_path: 文件路径（相对路径或绝对路径）

        Returns:
            bool: 删除结果

        Raises:
            BaseAppException: 删除失败时抛出
        """
        try:
            # 如果是相对路径，转换为物理路径
            if not os.path.isabs(file_path):
                file_path = self.get_physical_path(file_path)
            print(file_path, "======file_path=====")
            if os.path.exists(file_path):
                os.remove(file_path)
                return True
            return False
        except Exception as e:
            raise BaseAppException(msg=f"删除文件失败: {str(e)}")

    async def get_file_info(self, file_path: str) -> dict:
        """获取文件信息

        Args:
            file_path: 文件路径（相对路径或绝对路径）

        Returns:
            dict: 文件信息
        """
        # 如果是相对路径，转换为物理路径
        if not os.path.isabs(file_path):
            file_path = self.get_physical_path(file_path)

        if not os.path.exists(file_path):
            raise BaseAppException(msg=f"文件不存在: {file_path}")

        try:
            # 使用FileUtils获取详细信息
            return FileUtils.get_file_info(file_path)
        except Exception as e:
            # 如果FileUtils失败，返回基本信息
            file_stats = os.stat(file_path)
            return {
                "file_path": file_path,
                "size": file_stats.st_size,
                "created_time": file_stats.st_ctime,
                "modified_time": file_stats.st_mtime,
                "access_time": file_stats.st_atime,
            }

    async def upload_chunk_file(
        self,
        path: str,
        file: UploadFile,
        chunk_index: int,
        total_chunks: int,
        accept: Optional[List[str]] = None,
        max_size: int = 100,
    ) -> dict:
        """分片上传大文件

        Args:
            path: 保存路径
            file: 上传的文件分片
            chunk_index: 当前分片索引
            total_chunks: 总分片数
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            dict: 上传结果信息

        Raises:
            BaseAppException: 上传失败时抛出
        """
        # 验证文件类型
        if not accept:
            accept = self.ALL_ACCEPT

        if file.content_type not in accept:
            raise BaseAppException(msg=f"不支持的文件类型: {file.content_type}")

        # 生成临时文件路径 - 使用完整的物理路径
        temp_dir = os.path.join(self.UPLOAD_PATH, "temp")
        os.makedirs(temp_dir, exist_ok=True)

        # 使用文件唯一标识生成临时文件名
        file_identifier = f"{path}_{file.filename}"
        temp_file_path = os.path.join(temp_dir, file_identifier)

        # 保存当前分片
        try:
            with open(temp_file_path + f".part{chunk_index}", "wb") as f:
                f.write(await file.read())
        except Exception as e:
            raise BaseAppException(msg=f"保存分片失败: {str(e)}")
        finally:
            await file.close()

        # 检查是否所有分片都已上传
        all_chunks_uploaded = True
        for i in range(total_chunks):
            if not os.path.exists(temp_file_path + f".part{i}"):
                all_chunks_uploaded = False
                break

        # 如果所有分片都已上传，则合并文件
        if all_chunks_uploaded:
            try:
                # 生成最终文件的相对路径
                relative_file_path = self.generate_relative_path(path, file.filename)
                # 获取完整的物理路径
                final_file_path = self.get_physical_path(relative_file_path)

                # 确保目录存在
                os.makedirs(os.path.dirname(final_file_path), exist_ok=True)

                # 合并分片
                with open(final_file_path, "wb") as outfile:
                    for i in range(total_chunks):
                        chunk_path = temp_file_path + f".part{i}"
                        with open(chunk_path, "rb") as infile:
                            shutil.copyfileobj(infile, outfile)
                        # 删除分片文件
                        os.remove(chunk_path)

                return {"status": "completed", "file_path": relative_file_path, "message": "文件上传完成"}
            except Exception as e:
                raise BaseAppException(msg=f"合并文件失败: {str(e)}")

        return {
            "status": "pending",
            "chunk_index": chunk_index,
            "total_chunks": total_chunks,
            "message": "分片上传成功，等待其他分片",
        }

    async def _save_file_enhanced(
        self, path: str, file: UploadFile, accept: List[str], max_size: int, create_thumbnail: bool = False
    ) -> UploadResult:
        """增强版文件保存方法

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)
            create_thumbnail: 是否创建缩略图

        Returns:
            UploadResult: 保存结果

        Raises:
            BaseAppException: 保存失败时抛出
        """
        # 调用基类的高级保存方法（已经包含完整URL路径）
        result = await self.save_with_advanced_options(
            path=path, file=file, accept=accept, max_size=max_size, create_thumbnail=create_thumbnail
        )

        # 直接返回结果，不需要再次处理URL
        return result

    async def _save_file(self, path: str, file: UploadFile, accept: List[str], max_size: int) -> str:
        """保存文件的内部方法（保持向后兼容）

        Args:
            path: 保存路径
            file: 上传的文件
            accept: 允许的文件类型列表
            max_size: 最大文件大小(MB)

        Returns:
            str: 文件访问路径

        Raises:
            BaseAppException: 保存失败时抛出
        """
        result = await self._save_file_enhanced(path, file, accept, max_size)
        if not result.file_path:
            raise BaseAppException(msg="文件保存失败，未获取到文件路径")
        return result.file_path
